﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UtilZ.Dotnet.Ex.Log;
using UtilZ.Dotnet.Ex.Model;
using UtilZ.Dotnet.Ex.ZLog;

namespace UtilZ.Dotnet.Ex.Base
{
    /// <summary>
    /// 过期文件删除器
    /// </summary>
    public class ExpireFileCleaner : IDisposable
    {
        private readonly List<ExpireFileCleanerItem> _list = new List<ExpireFileCleanerItem>();
        private readonly object _lock = new object();
        private readonly int _intervalMilliseconds;
        private readonly ThreadEx _deleteExpireFileThread;
        private bool _disposed = false;

        /// <summary>
        /// 删除文件失败时触发事件
        /// </summary>
        public event EventHandler<EventArgs<IOException>> OnDeleteFailed;

        internal bool OnRaiseOnDeleteFailed(IOException ex)
        {
            var handler = this.OnDeleteFailed;
            if (handler != null)
            {
                handler(this, new EventArgs<IOException>(ex));
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="intervalMilliseconds">检查间隔,单位ms.小于等于0使用默认值600000</param>
        public ExpireFileCleaner(int intervalMilliseconds = 600000)
        {
            if (intervalMilliseconds <= 0)
            {
                intervalMilliseconds = 600000;
            }
            this._intervalMilliseconds = intervalMilliseconds;
            this._deleteExpireFileThread = new ThreadEx(this.DeleteExpireFileThreadMethod);
        }

        private void DeleteExpireFileThreadMethod(ThreadPara threadPara)
        {
            try
            {
                while (!this._disposed && !threadPara.Token.IsCancellationRequested)
                {
                    if (threadPara.WaitOne(this._intervalMilliseconds))
                    {
                        continue;
                    }

                    this.Check();
                }
            }
            catch (Exception ex)
            {
                Loger.Error(ex);
            }
        }

        /// <summary>
        /// 检查删除数据文件
        /// </summary>
        public void Check()
        {
            lock (this._lock)
            {
                if (this._disposed)
                {
                    return;
                }

                foreach (var item in this._list)
                {
                    item.DeleteExpireFile();
                }
            }
        }


        /// <summary>
        /// 添加一个用于过期检测的项
        /// </summary>
        /// <param name="item">要检查的项</param>
        public void Add(ExpireFileCleanerItem item)
        {
            lock (this._lock)
            {
                if (this._disposed)
                {
                    throw new ObjectDisposedException(this.GetType().Name);
                }

                item.SetExpireFileCleaner(this);
                this._list.Add(item);
            }
        }

        /// <summary>
        /// 清空项
        /// </summary>
        public void Clear()
        {
            lock (this._lock)
            {
                if (this._disposed)
                {
                    throw new ObjectDisposedException(this.GetType().Name);
                }

                this._list.Clear();
            }
        }

        /// <summary>
        /// 启动过期文件删除线程
        /// </summary>
        public void Start()
        {
            if (this._disposed)
            {
                throw new ObjectDisposedException(this.GetType().Name);
            }

            this._deleteExpireFileThread.Start();
        }

        /// <summary>
        /// 停止过期文件删除线程
        /// </summary>
        public void Stop()
        {
            if (this._disposed)
            {
                throw new ObjectDisposedException(this.GetType().Name);
            }

            this._deleteExpireFileThread.Stop();
        }

        /// <summary>
        /// 释放线程等非托管资源
        /// </summary>
        public void Dispose()
        {
            lock (this._lock)
            {
                if (this._disposed)
                {
                    return;
                }
                this._disposed = true;

                this._deleteExpireFileThread.Stop();
                this._deleteExpireFileThread.Dispose();
            }
        }

    }

    /// <summary>
    /// 过期文件删除器
    /// </summary>
    public class ExpireFileCleanerItem
    {
        private readonly DirectoryInfo _rootDirInfo = null;
        private readonly DriveInfo _rootDirDrive = null;

        private readonly long? _expireTimeMilliseconds;
        private readonly long? _availableSpaceSizeByte;
        private readonly Func<FileInfo, DateTime?> _getFileCheckTimeCallback;

        private string[] _extensions = null;
        /// <summary>
        /// 扩展名集合.当不为null且不为空时,只针对该值中的扩展名进行操作
        /// </summary>
        public string[] Extensions
        {
            get { return this._extensions; }
            set { this._extensions = value; }
        }

        private SearchOption _searchOption = SearchOption.AllDirectories;
        /// <summary>
        /// 搜索范围
        /// </summary>
        public SearchOption SearchOption
        {
            get { return this._searchOption; }
            set { this._searchOption = value; }
        }

        private bool _deleteEmptyDiretory = true;
        /// <summary>
        /// 是否删除空目录[true:删除;false:不删除]
        /// </summary>
        public bool DeleteEmptyDiretory
        {
            get { return this._deleteEmptyDiretory; }
            set { this._deleteEmptyDiretory = value; }
        }

        private ExpireFileCleaner _expireFileCleaner = null;
        internal void SetExpireFileCleaner(ExpireFileCleaner expireFileCleaner)
        {
            this._expireFileCleaner = expireFileCleaner;
        }


        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="rootPath">要清理文件的根目录</param>
        /// <param name="expireDays">过期时长,当不为null时保留该值天数内的数据,对超出该值天数外的数据进行删除</param>
        /// <param name="availableSpaceSizeGB">磁盘可用空间,单位GB.当不为null时将按时长升序删除更早创建的文件,直到磁盘可用空间大于等于该值结束</param>
        /// <param name="getFileCheckTimeCallback">获取文件检查时间回调函数,当为null时使用文件的创建时间和修改时间中的较小值.当不为null时回调函数的返回值为null,该文件忽略,不作做操作</param>
        public ExpireFileCleanerItem(string rootPath, double? expireDays, double? availableSpaceSizeGB, Func<FileInfo, DateTime?> getFileCheckTimeCallback = null)
        {
            if (!expireDays.HasValue && !availableSpaceSizeGB.HasValue)
            {
                throw new ArgumentNullException($"\"过期时长\"和\"磁盘可用空间\"不能同时为null");
            }

            this._rootDirInfo = new DirectoryInfo(rootPath);
            this._rootDirDrive = DriveInfo.GetDrives().FirstOrDefault(t =>
            {
                return t.DriveType == DriveType.Fixed && string.Equals(t.RootDirectory.FullName, this._rootDirInfo.Root.Name);
            });

            if (this._rootDirDrive == null)
            {
                throw new ArgumentException($"无效的根目录\"{rootPath}\",未找到匹配的磁盘根目录.");
            }

            if (expireDays.HasValue)
            {
                this._expireTimeMilliseconds = (long)(expireDays * 86400000);//86400000为24小时的毫秒数
            }

            if (availableSpaceSizeGB.HasValue)
            {
                this._availableSpaceSizeByte = UnitConverter.GByteToByte(availableSpaceSizeGB.Value);
            }

            this._getFileCheckTimeCallback = getFileCheckTimeCallback;
        }



        /// <summary>
        /// 删除过期的文件
        /// </summary>
        public void DeleteExpireFile()
        {
            try
            {
                if (!this._rootDirInfo.Exists)
                {
                    return;
                }

                if (this._expireTimeMilliseconds.HasValue)
                {
                    this._rootDirInfo.Refresh();
                    IEnumerable<FileInfo> fileInfos = this.GetFileInfoList();

                    if (this._getFileCheckTimeCallback == null)
                    {
                        foreach (var fileInfo in fileInfos)
                        {
                            try
                            {
                                if (fileInfo.CreationTime < fileInfo.LastWriteTime)
                                {
                                    if ((DateTime.Now - fileInfo.CreationTime).TotalMilliseconds >= this._expireTimeMilliseconds.Value)
                                    {
                                        if (fileInfo.Exists)
                                        {
                                            fileInfo.Delete();
                                        }
                                    }
                                }
                                else
                                {
                                    if ((DateTime.Now - fileInfo.LastWriteTime).TotalMilliseconds >= this._expireTimeMilliseconds.Value)
                                    {
                                        if (fileInfo.Exists)
                                        {
                                            fileInfo.Delete();
                                        }
                                    }
                                }
                            }
                            catch (IOException ioex)
                            {
                                if (!this._expireFileCleaner.OnRaiseOnDeleteFailed(ioex))
                                {
                                    ZLoger.Warn(ioex);
                                }
                            }
                        }
                    }
                    else
                    {
                        DateTime? checkTime;
                        foreach (var fileInfo in fileInfos)
                        {
                            checkTime = this._getFileCheckTimeCallback(fileInfo);
                            if (checkTime == null)
                            {
                                continue;
                            }

                            if ((DateTime.Now - checkTime.Value).TotalMilliseconds >= this._expireTimeMilliseconds.Value)
                            {
                                try
                                {
                                    if (fileInfo.Exists)
                                    {
                                        fileInfo.Delete();
                                    }
                                }
                                catch (IOException ioex)
                                {
                                    if (!this._expireFileCleaner.OnRaiseOnDeleteFailed(ioex))
                                    {
                                        ZLoger.Warn(ioex);
                                    }
                                }
                            }
                        }

                    }
                }

                if (this._availableSpaceSizeByte.HasValue && this._rootDirDrive.AvailableFreeSpace <= this._availableSpaceSizeByte.Value)
                {
                    this._rootDirInfo.Refresh();
                    FileInfo[] fileInfos = this.GetFileInfoList().OrderBy(t => { return t.CreationTime; }).ToArray();

                    foreach (var fileInfo in fileInfos)
                    {
                        try
                        {
                            fileInfo.Delete();

                            if (this._rootDirDrive.AvailableFreeSpace > this._availableSpaceSizeByte.Value)
                            {
                                break;
                            }
                        }
                        catch (IOException ioex)
                        {
                            if (!this._expireFileCleaner.OnRaiseOnDeleteFailed(ioex))
                            {
                                ZLoger.Warn(ioex);
                            }
                        }
                    }
                }

                if (this._deleteEmptyDiretory)
                {
                    this._rootDirInfo.Refresh();

                    DirectoryInfo[] dirInfos = this._rootDirInfo.GetDirectories();
                    foreach (var dirInfo in dirInfos)
                    {
                        try
                        {
                            this.PrimitiveDeleteEmptyDiretory(dirInfo);
                        }
                        catch (IOException ioex)
                        {
                            if (!this._expireFileCleaner.OnRaiseOnDeleteFailed(ioex))
                            {
                                ZLoger.Warn(ioex);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex);
            }
        }

        private bool PrimitiveDeleteEmptyDiretory(DirectoryInfo dirInfo)
        {
            if (dirInfo.GetFiles().Length > 0)
            {
                return false;
            }

            DirectoryInfo[] subDirInfos = dirInfo.GetDirectories();
            foreach (var subDirInfo in subDirInfos)
            {
                if (!this.PrimitiveDeleteEmptyDiretory(subDirInfo))
                {
                    return false;
                }
            }

            dirInfo.Delete();
            return true;
        }




        private IEnumerable<FileInfo> GetFileInfoList()
        {
            if (this._extensions == null || this._extensions.Length == 0)
            {
                return this._rootDirInfo.GetFiles("*.*", this._searchOption);
            }
            else
            {
                List<FileInfo> list = new List<FileInfo>();
                foreach (var extension in this._extensions)
                {
                    FileInfo[] fileInfos = this._rootDirInfo.GetFiles(extension, this._searchOption);
                    list.AddRange(fileInfos);
                }

                return list;
            }
        }

    }

}
